// Copyright John Maddock 2015.

// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <nil/crypto3/multiprecision/cpp_int.hpp>

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include "test.hpp"
#include <iostream>
#include <iomanip>

#ifdef BOOST_MSVC
#pragma warning(disable : 4127)
#endif
template<class T>
struct unchecked_type {
    typedef T type;
};

template<unsigned MinBits, unsigned MaxBits, nil::crypto3::multiprecision::cpp_integer_type SignType,
         nil::crypto3::multiprecision::cpp_int_check_type Checked, class Allocator,
         nil::crypto3::multiprecision::expression_template_option ExpressionTemplates>
struct unchecked_type<nil::crypto3::multiprecision::number<
    nil::crypto3::multiprecision::cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>,
    ExpressionTemplates>> {
    typedef nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_int_backend<MinBits, MaxBits, SignType,
                                                      nil::crypto3::multiprecision::unchecked, Allocator>,
        ExpressionTemplates>
        type;
};

template<class T>
T generate_random() {
    typedef typename unchecked_type<T>::type unchecked_T;

    static const unsigned limbs = std::numeric_limits<T>::is_specialized && std::numeric_limits<T>::is_bounded ?
                                      std::numeric_limits<T>::digits / std::numeric_limits<unsigned>::digits + 3 :
                                      20;

    static boost::random::uniform_int_distribution<unsigned> ui(0, limbs);
    static boost::random::mt19937 gen;
    unchecked_T val = gen();
    unsigned lim = ui(gen);
    for (unsigned i = 0; i < lim; ++i) {
        val *= (gen.max)();
        val += gen();
    }
    return val;
}

template<class T>
void test_round_trip_neg(T val, const boost::mpl::true_&) {
    // Try some negative values:
    std::vector<unsigned char> cv;
    T newval;
    val = -val;
    export_bits(val, std::back_inserter(cv), 8, false);
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(-val, newval);
}

template<class T>
void test_round_trip_neg(const T&, const boost::mpl::false_&) {
}

template<class T>
void test_round_trip(T val) {
    std::vector<unsigned char> cv;
    export_bits(val, std::back_inserter(cv), 8);
    T newval;
    import_bits(newval, cv.begin(), cv.end());
    BOOST_CHECK_EQUAL(val, newval);
    // Should get the same value if we reverse the bytes:
    std::reverse(cv.begin(), cv.end());
    newval = 0;
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);
    // Also try importing via pointers as these may memcpy:
    newval = 0;
    import_bits(newval, &cv[0], &cv[0] + cv.size(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 8, false);
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);
    std::reverse(cv.begin(), cv.end());
    newval = 0;
    import_bits(newval, cv.begin(), cv.end(), 8, true);
    BOOST_CHECK_EQUAL(val, newval);

    std::vector<boost::uintmax_t> bv;
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits);
    import_bits(newval, bv.begin(), bv.end());
    BOOST_CHECK_EQUAL(val, newval);
    // Should get the same value if we reverse the values:
    std::reverse(bv.begin(), bv.end());
    newval = 0;
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);
    // Also try importing via pointers as these may memcpy:
    newval = 0;
    import_bits(newval, &bv[0], &bv[0] + bv.size(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);

    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits, false);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);
    //
    // Try with an unconventional number of bits, to model some machine with guard bits:
    //
    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits - 3);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits - 3);
    BOOST_CHECK_EQUAL(val, newval);

    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits - 3, false);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits - 3, false);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 6);
    import_bits(newval, cv.begin(), cv.end(), 6);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 6, false);
    import_bits(newval, cv.begin(), cv.end(), 6, false);
    BOOST_CHECK_EQUAL(val, newval);

    test_round_trip_neg(val, boost::mpl::bool_<std::numeric_limits<T>::is_signed>());
}

template<class T>
void test_round_trip() {
    std::cout << std::hex;
    std::cerr << std::hex;
    for (unsigned i = 0; i < 1000; ++i) {
        T val = generate_random<T>();
        test_round_trip(val);
    }
    //
    // Bug cases.
    // See https://github.com/boostorg/multiprecision/issues/21
    T bug(1);
    bug << std::numeric_limits<T>::digits - 1;
    --bug;
    test_round_trip(bug);
}

int main() {
    test_round_trip<nil::crypto3::multiprecision::cpp_int>();
    test_round_trip<nil::crypto3::multiprecision::checked_int1024_t>();
    test_round_trip<nil::crypto3::multiprecision::checked_uint512_t>();
    test_round_trip<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_int_backend<
        64, 64, nil::crypto3::multiprecision::unsigned_magnitude, nil::crypto3::multiprecision::checked, void>>>();
    test_round_trip<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_int_backend<
        23, 23, nil::crypto3::multiprecision::unsigned_magnitude, nil::crypto3::multiprecision::checked, void>>>();
    return boost::report_errors();
}
